package com.plat.core.hibernate;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.SingleTableEntityPersister;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import com.plat.support.ModelSetup;
import com.plat.support.Page;
import com.plat.support.hibernate.HibernateModelSetup;
import com.plat.util.clazz.BeanUtils;
import com.plat.util.clazz.GenericsUtils;

@SuppressWarnings("unchecked")
public class HibernateGenericDao<T> {

    @Autowired
    @Qualifier("sessionFactory")
    private SessionFactory sessionFactory;

    protected Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    private Class<T> entityClass;

    public HibernateGenericDao() {
        entityClass = GenericsUtils.getSuperClassGenricType(getClass());
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    protected Class<T> getEntityClass() {
        if (entityClass == null) {
            entityClass = GenericsUtils.getSuperClassGenricType(getClass());
        }
        return entityClass;
    }

    public T get(Serializable id) {
        return (T) getSession().get(getEntityClass(), id);
    }

    public List<T> getAll() {
        return (List<T>) getSession().createCriteria(getEntityClass()).list();
    }

    /**
     * <br>
     * <b>功能：</b>获取排序后的所有对象<br>
     * <b>作者：</b>wench<br>
     * <b>日期：</b> 2012-7-29 <br>
     * @param entityClass
     * @param orderBy
     * @param isAsc
     * @return
     */
    public List<T> getAll(String orderBy, boolean isAsc) {
        Assert.hasText(orderBy);
        if (isAsc) {
            return getSession().createCriteria(getEntityClass()).addOrder(Order.asc(orderBy)).list();
        } else {
            return getSession().createCriteria(getEntityClass()).addOrder(Order.desc(orderBy)).list();
        }
    }

    public void save(Object o) {
        getSession().saveOrUpdate(o);
    }

    public void remove(Object o) {
        getSession().delete(o);
    }

    public void updateMerge(Object o) {
        getSession().merge(o);
    }

    public void removeById(Serializable id) {
        remove(get(id));
    }

    public Query createQuery(String hql, Object... values) {
        Assert.hasText(hql);
        Query query = getSession().createQuery(hql);
        for (int i = 0; i < values.length; i++) {
            query.setParameter(i, values[i]);
        }
        return query;
    }

    public Criteria createCriteria(Criterion... criterions) {
        Criteria criteria = getSession().createCriteria(getEntityClass());
        for (Criterion c : criterions) {
            criteria.add(c);
        }
        return criteria;
    }

    public Criteria createCriteria(String orderBy, boolean isAsc, Criterion... criterions) {
        Assert.hasText(orderBy);
        Criteria criteria = createCriteria(criterions);
        if (isAsc) {
            criteria.addOrder(Order.asc(orderBy));
        } else {
            criteria.addOrder(Order.desc(orderBy));
        }
        return criteria;
    }

    public List<T> find(String hql, Object... values) {
        Assert.hasText(hql);
        return findByValue(hql, values);
    }

    public List<T> findByValue(String hql, Object... values) {
        return getSession().createCriteria(hql).list();
    }

    public List<T> findBy(String propertyName, Object value) {
        Assert.hasText(propertyName);
        return createCriteria(Restrictions.eq(propertyName, value)).list();
    }

    public List<T> findBy(String propertyName, Object value, String orderBy, boolean isAsc) {
        Assert.hasText(propertyName);
        Assert.hasText(orderBy);
        return createCriteria(orderBy, isAsc, Restrictions.eq(propertyName, value)).list();
    }

    public T findUniqueBy(String propertyName, Object value) {
        Assert.hasText(propertyName);
        return (T) createCriteria(Restrictions.eq(propertyName, value)).uniqueResult();
    }

    public Page pagedQuery(String hql, int pageNo, int pageSize, Object... values) {
        Assert.hasText(hql);
        Assert.isTrue(pageNo >= 1, "pageNo should start from 1");
        // Count查询
        String countQueryString = " select count (*) " + removeSelect(removeOrders(hql));
        List countlist = findByValue(countQueryString, values);
        int totalCount = ((Long) countlist.get(0)).intValue();

        if (totalCount < 1)
            return new Page();
        // 实际查询返回分页对象
        int startIndex = Page.getStartOfPage(pageNo, pageSize);
        Query query = createQuery(hql, values);
        List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

        return new Page(startIndex, totalCount, pageSize, list);
    }

    public Integer getCount(Criteria criteria) {

        return (Integer) criteria.setProjection(Projections.rowCount()).uniqueResult();
    }

    public Page pagedQuery(Criteria criteria, int pageNo, int pageSize) {
        Assert.notNull(criteria);
        Assert.isTrue(pageNo >= 1, "pageNo should start from 1");
        CriteriaImpl impl = (CriteriaImpl) criteria;

        // 先把Projection和OrderBy条件取出来,清空两者来执行Count操作
        Projection projection = impl.getProjection();
        List<CriteriaImpl.OrderEntry> orderEntries;
        try {
            orderEntries = (List) BeanUtils.forceGetProperty(impl, "orderEntries");
            BeanUtils.forceSetProperty(impl, "orderEntries", new ArrayList());
        } catch (Exception e) {
            throw new InternalError(" Runtime Exception impossibility throw ");
        }

        // 执行查询
        int totalCount = (Integer) criteria.setProjection(Projections.rowCount()).uniqueResult();
        // 将之前的Projection和OrderBy条件重新设回去
        criteria.setProjection(projection);
        if (projection == null) {
            criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
        }

        try {
            BeanUtils.forceSetProperty(impl, "orderEntries", orderEntries);
        } catch (Exception e) {
            throw new InternalError(" Runtime Exception impossibility throw ");
        }
        // 返回分页对象
        if (totalCount < 1)
            return new Page();

        int startIndex = Page.getStartOfPage(pageNo, pageSize);
        List list = criteria.setFirstResult(startIndex).setMaxResults(pageSize).list();
        return new Page(startIndex, totalCount, pageSize, list);

    }

    public Page pagedQuery(int pageNo, int pageSize, Criterion... criterions) {
        Criteria criteria = createCriteria(criterions);
        return pagedQuery(criteria, pageNo, pageSize);
    }

    public Page pagedQuery(int pageNo, int pageSize, String orderBy, boolean isAsc, Criterion... criterions) {
        Criteria criteria = createCriteria(orderBy, isAsc, criterions);
        return pagedQuery(criteria, pageNo, pageSize);
    }

    public boolean isUnique(Object entity, String uniquePropertyNames) {
        Assert.hasText(uniquePropertyNames);
        Criteria criteria = createCriteria().setProjection(Projections.rowCount());
        String[] nameList = uniquePropertyNames.split(",");
        try {
            // 循环加入唯一列
            for (String name : nameList) {
                criteria.add(Restrictions.eq(name, PropertyUtils.getProperty(entity, name)));
            }

            // 以下代码为了如果是update的情况,排除entity自身.

            String idName = getIdName(getEntityClass());

            // 取得entity的主键值
            Serializable id = getId(getEntityClass(), entity);

            // 如果id!=null,说明对象已存在,该操作为update,加入排除自身的判断
            if (id != null)
                criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
        } catch (Exception e) {
            ReflectionUtils.handleReflectionException(e);
        }
        return (Integer) criteria.uniqueResult() == 0;
    }

    public Serializable getId(Class entityClass, Object entity) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Assert.notNull(entity);
        Assert.notNull(entityClass);
        return (Serializable) PropertyUtils.getProperty(entity, getIdName(entityClass));
    }

    public String getIdName(Class clazz) {
        Assert.notNull(clazz);
        ClassMetadata meta = sessionFactory.getClassMetadata(clazz);
        Assert.notNull(meta, "Class " + clazz + " not define in hibernate session factory.");
        String idName = meta.getIdentifierPropertyName();
        Assert.hasText(idName, clazz.getSimpleName() + " has no identifier property define.");
        return idName;
    }

    /**
     * 去除hql的select 子句，未考虑union的情况,用于pagedQuery.
     */
    private static String removeSelect(String hql) {
        Assert.hasText(hql);
        int beginPos = hql.toLowerCase().indexOf("from");
        Assert.isTrue(beginPos != -1, " hql : " + hql + " must has a keyword 'from'");
        return hql.substring(beginPos);
    }

    /**
     * 去除hql的orderby 子句，用于pagedQuery.
     */
    private static String removeOrders(String hql) {
        Assert.hasText(hql);
        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(hql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    /**
     * 构造Criteria的排序条件默认函数.可供其他查询函数调用
     * @param criteria
     *            Criteria实例.
     * @param sortMap
     *            排序条件.
     * @param entity
     *            entity对象,用于使用反射来获取某些属性信息
     */
    protected void sortCriteria(Criteria criteria, Map sortMap, Class entity) {
        if (!sortMap.isEmpty()) {
            for (Object o : sortMap.keySet()) {
                String fieldName = o.toString();
                String orderType = sortMap.get(fieldName).toString();

                // 处理嵌套属性如category.name,modify_user.id,暂时只处理一级嵌套
                if (fieldName.indexOf('.') != -1) {
                    String alias = StringUtils.substringBefore(fieldName, ".");
                    criteria.createAlias(alias, alias);
                }

                if ("asc".equalsIgnoreCase(orderType)) {
                    criteria.addOrder(Order.asc(fieldName));
                } else {
                    criteria.addOrder(Order.desc(fieldName));
                }
            }
        }
    }

    public String getColumnName(Class<T> entityClass, String propertyName) {
        SingleTableEntityPersister ep = (SingleTableEntityPersister) sessionFactory.getClassMetadata(entityClass);
        return ep.getPropertyColumnNames(propertyName)[0];
    }

    public Page pagedQuery(ModelSetup modelSetup, int pageNo, int pageSize) {
        HibernateModelSetup hibernateModelSetup = (HibernateModelSetup) modelSetup;
        Criteria criteria = this.createCriteria();
        // hibernateModelSetup.setup(criteria);
        return pagedQuery(criteria, pageNo, pageSize);
    }
}
